package nsalt.mem

import chisel3._
import chisel3.util._

import nsalt._
import nsalt.bus._
import nsalt.func._

class TransLookaside(implicit val conf: TransLookasideConf) extends Module with TransLookasideConst with CtrlStatusRegConst{
  
  val io = IO(new Bundle {
    val in = Flipped(new BusUncached(userBits = USER_BITS, addrBits = VIRT_MEM_ADDR_LEN))
    val out = new BusUncached(userBits = USER_BITS)

    val mem = new BusUncached(userBits = USER_BITS)
    val flush = Input(Bool())
    val csrMMU = new MemManagePort
    val cacheEmpty = Input(Bool())
    val ipf = Output(Bool())
  })

  val satp = WireInit(0.U(XLEN.W))
  // BoringUtils.addSink(satp, "CSRSATP")

  // tlb exec
  val tlbExec = Module(new TransLookasideExec)
  val tlbTable = Module(new TransLookasideTable)
  val tableUpdate = Wire(Bool())
  
  tlbExec.io.flush := io.flush
  tlbExec.io.satp := satp
  tlbExec.io.mem <> io.mem
  tlbExec.io.pageFault <> io.csrMMU
  tlbExec.io.group <> RegEnable(tlbTable.io.group, tableUpdate)
  tlbExec.io.groupReady := tlbTable.io.ready
  tlbTable.io.indexR := getIndex(io.in.req.bits.addr)
  tlbTable.io.write <> tlbExec.io.groupWrite
  
  io.ipf := false.B
  
  // meta reset
  val flushTLB = WireInit(false.B)
  // BoringUtils.addSink(flushTLB, "MOUFlushTLB")

  tlbTable.reset := reset.asBool || flushTLB

  // VM enable && io
  val vmEnable = satp.asTypeOf(satpBundle).mode === 8.U && (io.csrMMU.priviledgeMode < ModeM)

  def PipeLinkTLB[T <: Data](left: DecoupledIO[T], right: DecoupledIO[T], update: Bool, rightOutFire: Bool, isFlush: Bool, vmEnable: Bool) = {
    val valid = RegInit(false.B)
    when (rightOutFire) { 
      valid := false.B
    }
    // slightly different to ordinary PipeLink: vmEnable
    when (left.valid && right.ready && vmEnable) {
      valid := true.B 
    }
    when (isFlush) { 
      valid := false.B 
    }

    left.ready := right.ready
    right.bits <> RegEnable(left.bits, left.valid && right.ready)
    right.valid := valid //&& !isFlush

    update := left.valid && right.ready
  }

  PipeLinkTLB(io.in.req, tlbExec.io.in, tableUpdate, tlbExec.io.isFinish, io.flush, vmEnable)
  when(!vmEnable) {
    tlbExec.io.out.ready := true.B // let existed request go out
    io.out.req.valid := io.in.req.valid
    io.in.req.ready := io.out.req.ready
    io.out.req.bits.addr := io.in.req.bits.addr(PHYS_ADDR_LEN - 1, 0)
    io.out.req.bits.size := io.in.req.bits.size
    io.out.req.bits.command := io.in.req.bits.command
    io.out.req.bits.maskW := io.in.req.bits.maskW
    io.out.req.bits.dataW := io.in.req.bits.dataW
    io.out.req.bits.user.map(_ := io.in.req.bits.user.getOrElse(0.U))
  }.otherwise {
    io.out.req <> tlbExec.io.out
  }

  // lsu need dtlb signals
  if(TLB_NAME == "dtlb") {
    // io.in.res <> TransLookasideExec.io.in.res
    io.out.res.ready := true.B
    io.in.res.valid := tlbExec.io.out.valid
    io.in.res.bits.dataR := tlbExec.io.out.bits.addr
    io.in.res.bits.command := DontCare
    io.in.res.bits.user.map(_ := tlbExec.io.out.bits.user.getOrElse(0.U))
    val alreadyOutFinish = RegEnable(true.B, init=false.B, tlbExec.io.out.valid && !tlbExec.io.out.ready)
    // when(alreadyOutFinish && tlbExec.io.out.fire) { alreadyOutFinish := false.B}
    when(alreadyOutFinish && tlbExec.io.out.valid) { alreadyOutFinish := false.B}//???
    val tlbFinish = (tlbExec.io.out.valid && !alreadyOutFinish) || tlbExec.io.pageFault.isPageFault()
    // BoringUtils.addSource(tlbFinish, "DTLBFINISH")
    // BoringUtils.addSource(io.csrMMU.isPageFault(), "DTLBPF")
    // BoringUtils.addSource(vmEnable, "DTLBENABLE")
  }

  // instruction page fault
  if (TLB_NAME == "itlb") {
    io.out.res <> io.in.res
    when (tlbExec.io.ipf && vmEnable) {
      tlbExec.io.out.ready := io.cacheEmpty && io.in.res.ready
      io.out.req.valid := false.B
    }

    when (tlbExec.io.ipf && vmEnable && io.cacheEmpty) {
      io.in.res.valid := true.B
      io.in.res.bits.dataR := 0.U
      io.in.res.bits.command := BusCommand.READ_LAST
      io.in.res.bits.user.map(_ := tlbExec.io.in.bits.user.getOrElse(0.U))
      io.ipf := tlbExec.io.ipf
    }
  }
 }


object TransLookaside {
  def apply(in: BusUncached, mem: BusUncached, flush: Bool, csrMMU: MemManagePort)(implicit conf: TransLookasideConf) = {
    val tlb = Module(new TransLookaside)
    tlb.io.in <> in
    tlb.io.mem <> mem
    tlb.io.flush := flush
    tlb.io.csrMMU <> csrMMU
    tlb
  }
}